/*
 * @Author: xiaosihan
 * @Date: 2023-08-11 01:17:36
 * @Last Modified by: xiaosihan
 * @Last Modified time: 2024-10-20 07:37:39
 */

import { RepeatType } from '#/type';
import { fabric } from 'fabric';
import { IImageOptions } from 'fabric/fabric-impl';

// 图片
export default class FabricImage extends fabric.Image {
  constructor(
    element: string | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement,
    options?: Partial<FabricImage>,
  ) {
    super(element, options);

    if (options) {
      this.repeat = options.repeat || 'no_repeat';
      this.repeatSpaceX = options.repeatSpaceX || 0;
      this.repeatSpaceY = options.repeatSpaceY || 0;
      this.clipSvgUrl = options.clipSvgUrl || '';
    }

    this.setClicPath(this.clipSvgUrl);
  }

  declare _element: any;
  declare _filterScalingX: any;
  declare _filterScalingY: any;

  declare id: string;

  declare src: string;

  declare name: string;

  // declare canvas?: FabricCanvas;

  centeredRotation = true;

  originX = 'center';

  originY = 'center';

  objectCaching = true;

  // 缩小时不翻转
  lockScalingFlip = true;

  //平铺
  declare repeat: RepeatType;

  // 平铺的横向间距
  declare repeatSpaceX: number;

  // 平铺的纵向间距
  declare repeatSpaceY: number;

  declare imageSmoothingEnabled: boolean;

  //图片的遮罩用的svg地址
  declare clipSvgUrl: string;

  setSrc(src: string, callback?: Function, options?: IImageOptions){
    if(options){
      Object.assign(options,{
        crossOrigin: 'anonymous'
      });
    }else{
      options = {crossOrigin: 'anonymous'};
    }
    return super.setSrc(src, callback, options);
  }

  //设置遮罩
  setClicPath(clipSvgUrl: string = '') {
    this.clipSvgUrl = clipSvgUrl;
    if (this.clipSvgUrl) {
      fabric.loadSVGFromURL(this.clipSvgUrl, (objects, options) => {
        // // 将 SVG 元素转换为 Fabric 对象（关键步骤）
        const svgGroup = fabric.util.groupSVGElements(objects, options);

        // // 或者直接使用整个 SVG 组合（推荐）
        const clipPath = svgGroup;
        this.clipPath = clipPath;
        this.dirty = true;
        const { width, height } = clipPath;
        const minScale = Math.min(this.width! / width!, this.height! / height!);
        clipPath.left = minScale * width! / -2;
        clipPath.top = minScale * height! / -2;
        clipPath.scaleX = minScale;
        clipPath.scaleY = minScale;
        this.canvas?.renderAll();
      });
    } else {
      this.clipPath = undefined;
      this.dirty = true;
      this.canvas?.renderAll();
    }
  }

  //平铺缓存canvas
  static repeatCacheCanvas = document.createElement('canvas');

  //获取平铺缓存画布的上下文
  getRepeatCacheCtx() {
    if (this.canvas) {
      FabricImage.repeatCacheCanvas.width =
        this.canvas.getWidth() * Math.max(window.devicePixelRatio, 1);
      FabricImage.repeatCacheCanvas.height =
        this.canvas.getHeight() * Math.max(window.devicePixelRatio, 1);
    }
    return FabricImage.repeatCacheCanvas.getContext('2d')!;
  }

  // 拾取计算
  containsPoint(point: any, lines: any, absolute: any, calculate: any) {
    if (
      ['base_repeat', 'mirror_repeat', 'horizontal_repeat', 'vertical_repeat'].includes(this.repeat)
    ) {
      return true;
    } else {
      return super.containsPoint(point, lines, absolute, calculate);
    }
  }

  //是否是打印模式,  打印模式会处理边缘羽化的问题, 只有在导出生产文件的时候才会,设置为true
  print = false;

  //绘制图片
  _renderFill(ctx: CanvasRenderingContext2D): void {
    var elementToDraw = this._element;
    if (!elementToDraw) {
      return;
    }
    var scaleX = this._filterScalingX,
      scaleY = this._filterScalingY,
      w = this.width!,
      h = this.height!,
      min = Math.min,
      max = Math.max,
      cropX = max(this.cropX!, 0),
      cropY = max(this.cropY!, 0),
      elWidth = elementToDraw.naturalWidth || elementToDraw.width,
      elHeight = elementToDraw.naturalHeight || elementToDraw.height,
      sX = cropX * scaleX,
      sY = cropY * scaleY,
      sW = min(w * scaleX, elWidth - sX),
      sH = min(h * scaleY, elHeight - sY),
      x = -w / 2,
      y = -h / 2,
      maxDestW = min(w, elWidth / scaleX - cropX),
      maxDestH = min(h, elHeight / scaleY - cropY);
    if (elementToDraw) {
      //这2句代码是处理图案拼接的缝隙
      if (this.print) {
        const pixels = 1.5;
        ctx.drawImage(
          elementToDraw,
          sX,
          sY,
          sW,
          sH,
          x - pixels,
          y - pixels,
          maxDestW + 2 * pixels,
          maxDestH + 2 * pixels,
        );
      } else {
        ctx.drawImage(elementToDraw, sX, sY, sW, sH, x, y, maxDestW, maxDestH);
      }
    }
  }

  // 绘制元素
  drawObject(ctx: CanvasRenderingContext2D): void {
    // 普通绘制
    if ([undefined, 'no_repeat'].includes(this.repeat)) {
      super.drawObject(ctx);

      //4种平铺绘制
    } else if (
      ['base_repeat', 'mirror_repeat', 'horizontal_repeat', 'vertical_repeat'].includes(this.repeat)
    ) {
      const transform = ctx.getTransform();
      // 使用离屏渲染的方式 实现 轮廓裁剪
      const repeatCacheCtx = this.getRepeatCacheCtx();
      repeatCacheCtx.setTransform(transform);

      switch (this.repeat) {
        case 'base_repeat':
          this.drawBaseRepeatObject(repeatCacheCtx);
          break;
        case 'mirror_repeat':
          this.drawMirrorRepeatObject(repeatCacheCtx);
          break;
        case 'horizontal_repeat':
          this.drawHorizontalRepeatObject(repeatCacheCtx);
          break;
        case 'vertical_repeat':
          this.drawVerticalRepeatObject(repeatCacheCtx);
          break;
      }
      ctx.save();
      ctx.resetTransform();
      ctx.drawImage(FabricImage.repeatCacheCanvas, 0, 0);
      ctx.restore();
    }
  }

  //绘制基础平铺
  drawBaseRepeatObject(ctx: CanvasRenderingContext2D): void {
    let {
      width = 10,
      height = 10,
      scaleX = 1,
      scaleY = 1,
      repeatSpaceX = 0,
      repeatSpaceY = 0,
    } = this;
    width += repeatSpaceX;
    height += repeatSpaceY;
    const scaledWidth = this.getScaledWidth() + repeatSpaceX * scaleX;
    const scaledHeight = this.getScaledHeight() + repeatSpaceY * scaleY;
    const repeatNumber = Math.ceil((1024 * Math.SQRT2) / Math.min(scaledWidth, scaledHeight)) * 2;

    const transform = ctx.getTransform();
    for (let i = -repeatNumber; i < repeatNumber; i++) {
      for (let ii = -repeatNumber; ii < repeatNumber; ii++) {
        ctx.setTransform(transform);
        ctx.translate(width * i, height * ii);
        super.drawObject(ctx);
      }
    }
  }

  //绘制镜像平铺
  drawMirrorRepeatObject(ctx: CanvasRenderingContext2D): void {
    let {
      width = 10,
      height = 10,
      scaleX = 1,
      scaleY = 1,
      repeatSpaceX = 0,
      repeatSpaceY = 0,
    } = this;
    width += repeatSpaceX;
    height += repeatSpaceY;
    const scaledWidth = this.getScaledWidth() + repeatSpaceX * scaleX;
    const scaledHeight = this.getScaledHeight() + repeatSpaceY * scaleY;
    const repeatNumber = Math.ceil((1024 * Math.SQRT2) / Math.min(scaledWidth, scaledHeight)) * 2;

    const transform = ctx.getTransform();
    for (let i = -repeatNumber; i < repeatNumber; i++) {
      for (let ii = -repeatNumber; ii < repeatNumber; ii++) {
        ctx.setTransform(transform);
        ctx.scale(i % 2 == 0 ? 1 : -1, ii % 2 == 0 ? 1 : -1);
        ctx.translate(width * i, height * ii);
        super.drawObject(ctx);
      }
    }
  }

  //绘制横向平铺
  drawHorizontalRepeatObject(ctx: CanvasRenderingContext2D): void {
    let {
      width = 10,
      height = 10,
      scaleX = 1,
      scaleY = 1,
      repeatSpaceX = 0,
      repeatSpaceY = 0,
    } = this;
    width += repeatSpaceX;
    height += repeatSpaceY;
    const scaledWidth = this.getScaledWidth() + repeatSpaceX * scaleX;
    const scaledHeight = this.getScaledHeight() + repeatSpaceY * scaleY;
    const repeatNumber = Math.ceil((1024 * Math.SQRT2) / Math.min(scaledWidth, scaledHeight)) * 2;

    const transform = ctx.getTransform();
    for (let i = -repeatNumber; i < repeatNumber; i++) {
      for (let ii = -repeatNumber; ii < repeatNumber; ii++) {
        ctx.setTransform(transform);
        ctx.translate(width * (i - (ii % 2 == 0 ? 0 : 0.5)), height * ii);
        super.drawObject(ctx);
      }
    }
  }

  // 绘制纵向平铺
  drawVerticalRepeatObject(ctx: CanvasRenderingContext2D): void {
    let {
      width = 10,
      height = 10,
      scaleX = 1,
      scaleY = 1,
      repeatSpaceX = 0,
      repeatSpaceY = 0,
    } = this;
    width += repeatSpaceX;
    height += repeatSpaceY;
    const scaledWidth = this.getScaledWidth() + repeatSpaceX * scaleX;
    const scaledHeight = this.getScaledHeight() + repeatSpaceY * scaleY;
    const repeatNumber = Math.ceil((1024 * Math.SQRT2) / Math.min(scaledWidth, scaledHeight)) * 2;

    const transform = ctx.getTransform();
    for (let i = -repeatNumber; i < repeatNumber; i++) {
      for (let ii = -repeatNumber; ii < repeatNumber; ii++) {
        ctx.setTransform(transform);
        ctx.translate(width * i, height * (ii - (i % 2 == 0 ? 0 : 0.5)));
        super.drawObject(ctx);
      }
    }
  }

  toObject(propertiesToInclude: Array<string> = []) {
    const data = super.toObject(
      propertiesToInclude.concat([
        'id',
        'name',
        'repeat',
        'repeatSpaceX',
        'repeatSpaceY',
        'lockMovementX',
        'lockMovementY',
        'lockRotation',
        'lockScalingX',
        'lockScalingY',
        'lockSkewingX',
        'lockSkewingY',
        'clipSvgUrl',
      ]),
    );
    return data;
  }
}

fabric.Image = FabricImage;
